package cn.wizzer.app.web.modules.controllers.user;

import cn.wizzer.app.sys.modules.models.SysParam;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.user.modules.models.User;
import cn.wizzer.app.user.modules.models.UserFeedback;
import cn.wizzer.app.user.modules.models.constant.UserConstant;
import cn.wizzer.app.user.modules.models.enums.EquipmentEnum;
import cn.wizzer.app.user.modules.models.enums.PhoneCodeEnum;
import cn.wizzer.app.user.modules.services.UserFeedbackService;
import cn.wizzer.app.user.modules.services.UserLogService;
import cn.wizzer.app.user.modules.services.UserService;
import cn.wizzer.app.web.commons.ext.websocket.main.base.NettyCache;
import cn.wizzer.app.web.commons.filter.ApiTokenFilter;
import cn.wizzer.app.web.commons.ulog.annotation.ULog;
import cn.wizzer.app.web.commons.utils.TokenUtil;
import cn.wizzer.framework.util.Result;
import cn.wizzer.framework.base.model.enums.BizExceptionEnum;
import cn.wizzer.framework.util.param.ParamVerify;
import com.alibaba.dubbo.config.annotation.Reference;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.util.ByteSource;
import org.nutz.dao.Cnd;
import org.nutz.img.Images;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.*;
import redis.clients.jedis.ScanParams;

import java.awt.image.BufferedImage;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@IocBean
@At("/api/user")
public class ApiUserController {
    private final static Log log = Logs.get();

    @Inject
    private PropertiesProxy conf;

    @Reference
    @Inject
    private UserService userService;
    @Reference
    @Inject
    private UserLogService userLogService;

    @Reference
    @Inject
    private SysParamService sysParamService;

    @Inject
    private TokenUtil tokenUtil;

    @Reference
    @Inject
    private UserFeedbackService feedbackService;


    @Inject
    private RedisService redisService;

    @Inject
    @Reference
    private SysParamService paramService;

    private final Pattern userNamePattern = Pattern.compile("^[a-zA-Z0-9]{6,16}$");

    @At
    @Ok("json:full")
    @ULog(tag = "用户登录", msg = "用户名: ${userName}, 登录设备: ${equipment}")
    public Result login(@ParamVerify(required = true, name = "userName") String userName,
                        @ParamVerify(required = true, name = "password") String password,
                        @Param(value = "equipment", df = "MOBILE") EquipmentEnum equipment) {
        Result result = userService.login(userName, password, equipment, Lang.getIP(Mvcs.getReq()));
        //登录成功，原来的ws要销毁
        if (result.getCode() == 0) {
            NutMap res = (NutMap) result.getData();
            NettyCache.globalDel(res.get("appid").toString());
        }
        return result;
    }

    /**
     * 退出登录
     *
     * @return
     */
    @At
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "退出登录")
    public Result logout() {
        String appid = Mvcs.getReq().getHeader("appid");
        User user = userService.fetch(appid);
        if (!user.isOnline()) {
            return Result.success();
        }
        // 设置状态为离线
        user.setOnline(false);
        //更新登录时长
        user.setLoginDuration(userService.calculateLoginDuration(user));
        userService.update(user);
        redisService.del(("api_token:" + user.getId()));
        return Result.success();
    }

    /**
     * 用户修改登录密码
     *
     * @param newPassword
     * @param password
     * @return
     */
    @At
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户登录修改密码")
    public Result revisePassword(@ParamVerify(name = "newPassword", required = true) String newPassword,
                                 @ParamVerify(name = "password", required = true) String password) {
        String id = Mvcs.getReq().getHeader("appid");

        /*
         * 校验原密码是否正确
         * 修改密码
         * */
        User user = userService.fetch(Cnd.where("id", "=", Strings.trim(id)));
        String encryption = new Sha256Hash(password, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
        if (!encryption.equals(user.getPassword())) {
            return Result.error(BizExceptionEnum.PWD_VERIFICATION_ERROR);
        }
        //对密码进行加密
        String newPwd = new Sha256Hash(newPassword, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
        //新登录密码验证
        userService.newPwdValidation(user, newPwd, null);
        user.setPassword(newPwd);
        userService.updateIgnoreNull(user);
        return Result.success(BizExceptionEnum.UPDATE_SUCCESS);
    }

    /**
     * 根据用户id修改密码
     *
     * @param headPicture
     * @return
     */
    @At
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户修改头像")
    public Result revisePicture(@Param("headPicture") String headPicture) {
        String id = Mvcs.getReq().getHeader("appid");
        User user = userService.fetch(id);
        user.setHeadPicture(headPicture);
        userService.updateIgnoreNull(user);
        return Result.success();
    }

    /**
     * 根据用户id修改昵称
     *
     * @param nickName
     * @return
     */
    @At
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户修改昵称", msg = "新昵称: ${nickName}")
    public Result reviseNickName(@Param("nickName") String nickName) {
        try {
            nickName = Strings.trim(nickName);
            // ^[a-zA-Z0-9]{6,16}$   6到16位只允许英文、数字
            Matcher m = userNamePattern.matcher(nickName);
            if (!m.matches()) {
                return Result.error(BizExceptionEnum.NICK_NAME_IRREGULAR);
            }
            String appid = Mvcs.getReq().getHeader("appid");
            User user = userService.fetch(Cnd.where("id", "=", Strings.trim(appid)));
            if (user.isVisitor()) {
                return Result.error(BizExceptionEnum.ERROR);
            }
            //判断昵称是否重复
            int num = userService.count(Cnd.where("nickName", "=", Strings.trim(nickName)));
            if (num > 0) {
                return Result.error(BizExceptionEnum.NICK_NAME_EXISTED);
            }
            user.setNickName(nickName);
            userService.update(user);
            return Result.success(BizExceptionEnum.UPDATE_SUCCESS);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return Result.error(BizExceptionEnum.UPDATE_FAIL);
    }

    /**
     * 根据用户id修改签名
     *
     * @param sign String
     * @return Result
     */
    @At
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户修改签名")
    public Result reviseSign(@Param("sign") String sign) {
        try {
            String appid = Mvcs.getReq().getHeader("appid");
            User user = userService.fetch(appid);
            if (user.isVisitor()) {
                return Result.error(BizExceptionEnum.ERROR);
            }
            user.setSign(sign);
            userService.update(user);
            return Result.success(BizExceptionEnum.UPDATE_SUCCESS);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return Result.error(BizExceptionEnum.UPDATE_FAIL);
    }

    /**
     * 用户注册
     *
     * @param code       图形验证码
     * @param userName   注册用户名
     * @param password   密码
     * @param inviteCode 邀请码
     * @return
     */
    @At
    @Ok("json")
    @ULog(tag = "用户注册", msg = "用户名: ${userName} 邀请码: ${inviteCode} 手机号前缀: ${phonePrefix} 手机号:${phone}")
    public Result register(@ParamVerify(name = "code", required = true) String code,
                           @ParamVerify(name = "phonePrefix", required = true) String phonePrefix,
                           @ParamVerify(name = "phone", required = true) String phone,
                           @ParamVerify(name = "userName", required = true) String userName,
                           @ParamVerify(name = "password", required = true) String password,
                           @Param("inviteCode") String inviteCode) {
        userName = Strings.trim(userName);
        // ^[a-zA-Z0-9]{6,16}$   6到16位只允许英文、数字
        Matcher m = userNamePattern.matcher(userName);
        if (!m.matches()) {
            return Result.error(BizExceptionEnum.ILLEGAL_OPERATION);
        }
        phonePrefix = phonePrefix.replaceAll("\\+", "").trim();
        phone = phone.trim().replaceFirst("^0*", "");
        // 判断验证码否一致
        if (!sysParamService.fetch().isDebug()) {
            if (!Objects.equals(redisService.get(PhoneCodeEnum.FIX + PhoneCodeEnum.REGISTER.VALUE + ":" + phonePrefix + "&" + phone), code)) {
                return Result.error(BizExceptionEnum.CODE_AND_PHONE_INCONSISTENT);
            }
        }
        // 一个ip最多只能注册5个账号
        if (userService.count(Cnd.where("ip", "=", Lang.getIP(Mvcs.getReq()))) > 5) {
            return Result.error(BizExceptionEnum.REGISTER_IP_FREQUENTLY);
        }
        User user = new User();
        user.setUserName(userName);
        user.setNickName(userName);
        user.setPassword(password);
        user.setPhonePrefix(phonePrefix);
        user.setPhone(phone);
        // 各种校验在insertUser方法中
        userService.insertUser(user, inviteCode);
        return Result.success(BizExceptionEnum.REGISTER_SUCCESS);
    }


    /**
     * 获取图形验证码
     *
     * @param randomStr 随机字符串
     * @param w         图片宽(默认200)
     * @param h         图片高(默认60)
     * @return
     */
    @At("/captcha")
    @Ok("raw:png")
    public BufferedImage captcha(@ParamVerify(name = "randomStr", required = true) String randomStr,
                                 @Param("w") int w,
                                 @Param("h") int h) {
        //长或宽为0?重置为默认长宽.
        if (w * h < 1) {
            w = 200;
            h = 60;
        }
        String text = R.captchaNumber(4);
        redisService.setex("APICaptcha:" + randomStr, 300, text);
        return Images.createCaptcha(text, w, h, null, null, null);
    }


    /**
     * 查询用户是否设置交易密码
     *
     * @return
     */
    @At
    @Ok("json")
    @Filters(@By(type = ApiTokenFilter.class))
    public Result isSetTradePwd() {
        User user = userService.fetch(Mvcs.getReq().getHeader("appid"));
        if (user.isVisitor()) {
            return Result.error(BizExceptionEnum.ERROR);
        }
        if (user == null) {
            return Result.error(BizExceptionEnum.USER_NOT_EXIST);
        }
        return Result.success().addData(Strings.isNotBlank(user.getTransactionPwd()));
    }


    /**
     * 设置交易密码
     *
     * @param tradePwd 交易密码(兼容旧版本让老用户设置真实姓名，所以非必传)
     * @param realName 真实姓名
     * @return
     */
    @At
    @Ok("json")
    @Filters(@By(type = ApiTokenFilter.class))
    @ULog(tag = "用户设置交易密码")
    public Result setTradePwd(@Param("tradePwd") String tradePwd,
                              @ParamVerify(name = "realName", required = true) String realName) {
        User user = userService.fetch(Mvcs.getReq().getHeader("appid"));
        if (user == null) {
            return Result.error(BizExceptionEnum.USER_NOT_EXIST);
        }
        if (user.isVisitor()) {
            return Result.error(BizExceptionEnum.ILLEGAL_OPERATION);
        }
        // 内部账号是否不让这是交易密码
        if (user.isInside()) {
            return Result.error(BizExceptionEnum.ILLEGAL_OPERATION);
        }
        // 真实姓名是否存在
        if (userService.count(Cnd.where("realName", "=", realName).and("id", "!=", user.getId())) > 0) {
            return Result.error(BizExceptionEnum.REAL_NAME_EXIST);
        }
        user.setRealName(realName);
        // 兼容旧版本让老用户设置真实姓名，所以非必传
        if (Strings.isNotBlank(tradePwd)) {
            String newPwd = new Sha256Hash(tradePwd, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
            // 新交易密码验证
            userService.newPwdValidation(user, null, newPwd);
            // 设置交易密码
            user.setTransactionPwd(newPwd);
        }
        userService.update(user);
        return Result.success();
    }


    /**
     * 用户修改交易密码
     *
     * @param tradePwd    旧交易密码
     * @param newTradePwd 新交易密码
     * @return
     */
    @At
    @Ok("json")
    @Filters(@By(type = ApiTokenFilter.class))
    @ULog(tag = "用户修改交易密码")
    public Result updateTradePwd(@ParamVerify(name = "tradePwd", required = true) String tradePwd,
                                 @ParamVerify(name = "newTradePwd", required = true) String newTradePwd) {
        User user = userService.fetch(Mvcs.getReq().getHeader("appid"));
        if (user == null) {
            return Result.error(BizExceptionEnum.USER_NOT_EXIST);
        }
        if (user.isVisitor()) {
            return Result.error(BizExceptionEnum.ILLEGAL_OPERATION);
        }
        String oldTradePwd = user.getTransactionPwd();
        // 旧交易密码转加密后校验是否正确
        String oldPwd = new Sha256Hash(tradePwd, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
        if (!oldPwd.equals(oldTradePwd)) {
            return Result.error(BizExceptionEnum.PWD_VERIFICATION_ERROR);
        }
        // 修改新的交易密码
        String newPwd = new Sha256Hash(newTradePwd, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
        // 新交易密码验证
        userService.newPwdValidation(user, null, newPwd);
        // 设置交易密码
        user.setTransactionPwd(newPwd);
        userService.update(user);
        return Result.success();
    }


    /**
     * 在线人数统计
     */
    @At
    @Ok("json")
    public Result onlineCount(EquipmentEnum equipment) {
        ScanParams scanParams = new ScanParams();
        scanParams.match("user:token:*" + (equipment == null ? "" : equipment));
        scanParams.count(Integer.MAX_VALUE);
        //用户id去重
        List<String> result = redisService.scan("0", scanParams).getResult().stream().map(key -> {
            String replace = key.replace("user:token:", "");
            return replace.substring(0, replace.indexOf(":"));
        }).distinct().collect(Collectors.toList());
        return Result.success().addData(result.size());
    }


    /**
     * 设置邮箱
     *
     * @param email
     * @return
     */
    @At
    @Ok("json")
    @Filters(@By(type = ApiTokenFilter.class))
    @ULog(tag = "用户设置邮箱", msg = "邮箱: ${email}")
    public Result setEmail(@ParamVerify(name = "email", required = true) String email) {
        String userId = Mvcs.getReq().getHeader("appid");
        // 游客账号不能修改邮箱
        User user = userService.fetch(userId);
        if (user.isVisitor()) {
            return Result.error(BizExceptionEnum.ILLEGAL_OPERATION);
        }
        // 查询邮箱是否存在
        if (userService.count(Cnd.where("email", "=", email).and("id", "!=", userId)) > 0) {
            return Result.error(BizExceptionEnum.EMAIL_EXIST);
        }
        user.setEmail(email);
        userService.update(user);
        return Result.success(BizExceptionEnum.UPDATE_SUCCESS);
    }


    /**
     * 用户修改手机号
     *
     * @param phonePrefix 手机号前缀
     * @param phone       手机号
     * @return
     */
    @At
    @Ok("json")
    @Filters(@By(type = ApiTokenFilter.class))
    @ULog(tag = "用户修改手机号", msg = "用户名: ${userName} 邀请码: ${inviteCode} 手机号前缀: ${phonePrefix} 手机号:${phone}")
    public Result setPhone(@ParamVerify(name = "phonePrefix", required = true) String phonePrefix,
                           @ParamVerify(name = "phone", required = true) String phone,
                           @ParamVerify(name = "code", required = true) String code) {
        phonePrefix = phonePrefix.replaceAll("\\+", "").trim();
        phone = phone.trim().replaceFirst("^0*", "");
        SysParam sysParam = sysParamService.fetch();
        if (!sysParam.isDebug()) {
            // 判断验证码是否一致
            if (!Objects.equals(redisService.get(PhoneCodeEnum.FIX + PhoneCodeEnum.UPDATEPHONE.VALUE + ":" + phonePrefix + "&" + phone), code)) {
                return Result.error(BizExceptionEnum.CODE_AND_PHONE_INCONSISTENT);
            }
        }
        String userId = Mvcs.getReq().getHeader("appid");
        // 游客账号不能设置手机号
        User user = userService.fetch(userId);
        if (null == user) {
            return Result.error(BizExceptionEnum.USER_NOT_EXIST);
        }
        if (user.isVisitor()) {
            return Result.error(BizExceptionEnum.ILLEGAL_OPERATION);
        }
        // 查询该手机号是否存在，不包括自己
        int count = userService.count(Cnd.where("phonePrefix", "=", phonePrefix)
                .and("phone", "=", phone)
                .and("id", "!=", userId));
        if (count > 0) {
            return Result.error(BizExceptionEnum.PHONE_EXIST);
        }
        user.setPhonePrefix(phonePrefix);
        user.setPhone(phone);
        userService.update(user);
        return Result.success(BizExceptionEnum.UPDATE_SUCCESS);
    }

    @At
    @Ok("json")
    @ULog(tag = "用户反馈")
    @POST
    public Result feedback(@ParamVerify(name = "content", required = true) String content,
                           @ParamVerify(name = "name", required = true) String name,
                           @ParamVerify(name = "email", required = true) String email) {
        UserFeedback feedback = new UserFeedback();
        feedback.setContent(content);
        feedback.setName(name);
        feedback.setEmail(email);
        feedbackService.insert(feedback);

        return Result.success();
    }


    /**
     * 游客登录
     *
     * @return
     */
    @At
    @Ok("json")
    @ULog(tag = "游客登录")
    public Result visitorLogin() {
//        return userService.visitorLogin(Lang.getIP(Mvcs.getReq()));
        return null;
    }


    /**
     * 用户重置登录密码
     *
     * @param phonePrefix
     * @param phone
     * @param code
     * @param newLoginPwd
     * @return
     */
    @At("/resetLoginPwd")
    @Ok("json")
    @ULog(tag = "用户重置登录密码", msg = "手机号前缀: ${phonePrefix} 手机号:${phone}")
    public Result resetLoginPwd(@ParamVerify(name = "phonePrefix", required = true) String phonePrefix,
                                @ParamVerify(name = "phone", required = true) String phone,
                                @ParamVerify(name = "code", required = true) String code,
                                @ParamVerify(name = "newLoginPwd", required = true) String newLoginPwd) {
        phonePrefix = phonePrefix.replaceAll("\\+", "").trim();
        phone = phone.trim().replaceFirst("^0*", "");
        SysParam sysParam = sysParamService.fetch();
        if (!sysParam.isDebug()) {
            // 校验验证码
            if (!Objects.equals(redisService.get(PhoneCodeEnum.FIX + PhoneCodeEnum.RESETLOGINPWD.VALUE + ":" + phonePrefix + "&" + phone), code)) {
                return Result.error(BizExceptionEnum.CODE_AND_PHONE_INCONSISTENT);
            }
        }
        User user = userService.fetch(Cnd.where("phonePrefix", "=", phonePrefix).and("phone", "=", phone));
        if (null == user) {
            return Result.error(BizExceptionEnum.USER_NOT_EXIST);
        }
        String newPwd = new Sha256Hash(newLoginPwd, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
        //新登录密码验证
        userService.newPwdValidation(user, newPwd, null);
        user.setPassword(newPwd);
        userService.update(user);
        //重置登录次数
        redisService.del(UserConstant.REDIS_KEY_LOGIN_ERR + user.getUserName());
        return Result.success();
    }

    /**
     * 用户重置交易密码
     *
     * @param phonePrefix
     * @param phone
     * @param code
     * @param newTradePwd
     * @return
     */
    @At("/resetTradePwd")
    @Ok("json")
    @ULog(tag = "用户重置交易密码", msg = "手机号前缀: ${phonePrefix} 手机号:${phone}")
    public Result resetTradePwd(@ParamVerify(name = "phonePrefix", required = true) String phonePrefix,
                                @ParamVerify(name = "phone", required = true) String phone,
                                @ParamVerify(name = "code", required = true) String code,
                                @ParamVerify(name = "newTradePwd", required = true) String newTradePwd) {
        phonePrefix = phonePrefix.replaceAll("\\+", "").trim();
        phone = phone.trim().replaceFirst("^0*", "");
        SysParam sysParam = sysParamService.fetch();
        if (!sysParam.isDebug()) {
            // 校验验证码
            if (!Objects.equals(redisService.get(PhoneCodeEnum.FIX + PhoneCodeEnum.RESETTRADEPWD.VALUE + ":" + phonePrefix + "&" + phone), code)) {
                return Result.error(BizExceptionEnum.CODE_AND_PHONE_INCONSISTENT);
            }
        }
        User user = userService.fetch(Cnd.where("phonePrefix", "=", phonePrefix).and("phone", "=", phone));
        if (null == user) {
            return Result.error(BizExceptionEnum.USER_NOT_EXIST);
        }
        String newPwd = new Sha256Hash(newTradePwd, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
        //新登录密码验证
        userService.newPwdValidation(user, null, newPwd);
        user.setTransactionPwd(newPwd);
        userService.update(user);
        return Result.success();
    }


}









